// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
 * Description: Euler Hybrid Memory Management
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/mmzone.h>
#include <linux/mm_types.h>
#include <linux/uaccess.h>
#include <linux/rmap.h>
#include <linux/pagemap.h>
#include <linux/memory.h>
#include <linux/debugfs.h>
#include <linux/gfp.h>
#include <linux/ras_mem_offline.h>

#include "hpmm_mgr.h"
#include "hpmm_persist.h"

struct uce_record *uce_record_alloc(struct hpmm_mgr_context *hpmm_mgr)
{
	struct uce_record_head *head;
	unsigned long index;

	head = hpmm_mgr->hpmm_mgr_info[HPMM_UCE_RECORD_HEAD].uce_record_head;
	index = find_and_set_bit(head->bitmap, head->last, head->max_index);
	if (index == -1) {
		pr_err("find useable uce index failed\n");
		return NULL;
	}
	return head->records + index;
}

struct uce_record *hpmm_record_uce(unsigned long pfn, unsigned long type)
{
	int node;
	struct hpmm_persist_device *dev;
	struct uce_record *record;

	node = page_to_nid(pfn_to_page(pfn));
	dev = find_hpmm_persist_device(node);
	if (!dev)
		return NULL;

	record = uce_record_alloc((struct hpmm_mgr_context *)dev->mgr_addr);
	if (!record)
		return NULL;

	record->pfn = pfn;
	record->type = type;
	return record;
}

static int hpmm_uce_callback(struct notifier_block *self,
			       unsigned long action, void *data)
{
	unsigned long pfn = *(unsigned long *)data;
	struct page *page = pfn_to_page(pfn);
	int ret = NOTIFY_DONE;

	if (!is_node_pmem(page_to_nid(page)))
		return NOTIFY_DONE;
	if (check_and_clear_meta(page)) {
		panic("Memory failure on page in BBU meta area\n");
		return NOTIFY_STOP;
	}
	if (page_mapcount(page) > 0) {
		struct uce_record *record;

		record = hpmm_record_uce(pfn, action);
		if (!record) {
			pr_err("record uce failed type %ld\n", action);
			return NOTIFY_DONE;
		}

		switch (action) {
		case FMA_MEM_OFFLINE_NOTIFY_PAGE:
			pr_info("FMA_MEM_OFFLINE_NOTIFY_PAGE\n");
			ret = NOTIFY_STOP;
			break;
		case FMA_MEM_OFFLINE_NOTIFY_SRAO:
			pr_info("FMA_MEM_OFFLINE_NOTIFY_SRAO\n");
			ret = NOTIFY_STOP;
			break;
		case FMA_MEM_OFFLINE_NOTIFY_SRAR:
			pr_info("FMA_MEM_OFFLINE_NOTIFY_SRAR\n");
			force_sig(SIGKILL);
			ret = NOTIFY_STOP;
			break;
		default:
			ret = NOTIFY_DONE;
			break;
		}
	} else {
		hpmm_record_uce(pfn, action);
		ret = NOTIFY_STOP;
	}
	return ret;
}

static struct notifier_block hpmm_uce_nb = {
	.notifier_call = hpmm_uce_callback,
};

int hpmm_uce_init(void)
{
#ifdef CONFIG_DEBUG_FS
	hpmm_create_debugfs();
#endif
	return fma_register_memory_offline_notifier(&hpmm_uce_nb);
}

void hpmm_uce_exit(void)
{
#ifdef CONFIG_DEBUG_FS
	hpmm_remove_debugfs();
#endif
	return fma_unregister_memory_offline_notifier(&hpmm_uce_nb);
}
